package com.tma.implementation;

import java.util.ArrayList;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBCursor;
import com.mongodb.DBObject;
import com.mongodb.Mongo;
import com.tma.model.GraphObject;
import com.tma.service.GraphStorageService;

public class MongodbBasedStorage implements GraphStorageService{
	
	private static final Logger LOGGER = Logger.getLogger(MongodbBasedStorage.class.getName());
	private String m_dbHostAddress;
	private int m_dbHostPort;
	private Mongo m_mongo = null;
	private DB m_db = null;
	private DBCollection m_collection = null;
	
	private static final String GRAPHDBNAME = "graphdb";
	private static final String GRAPHCOLLECTION = "graph";
	private static final String NAME_FILED = "name";
	private static final String DATA_FILED = "data";
	
	public MongodbBasedStorage(String mongoHostAddress, int hostPort) {
		m_dbHostAddress = mongoHostAddress;
		m_dbHostPort = hostPort;
	}
	
	/**
	 * @method: open the connection to mongo database
	 * @return
	 */
	private boolean openConnection()
	{
		boolean result = false;
		if(m_mongo != null)
		{
			LOGGER.log(Level.SEVERE,"Failed to open connection to mongo" +
					"because mongo connection has already established");
			result =  false;
		}
		else
		{
			try {
				m_mongo = new Mongo(m_dbHostAddress, m_dbHostPort);
				 
				// get database from MongoDB,
				// if database doesn't exists, mongoDB will create it automatically
				m_db = m_mongo.getDB(GRAPHDBNAME);
				if(m_db != null)
				{
					// Get collection from MongoDB
					// if collection doesn't exists, mongoDB will create it automatically
					m_collection = m_db.getCollection(GRAPHCOLLECTION);
					if(m_collection != null)
					{
						result = true;
					}
					else
					{
						result = false;
					}
				}
				else
				{									
					result = false;
				}
				
			} catch (Exception ex) {
				
				LOGGER.log(Level.SEVERE, "Failed to open connection to mongo, exeption error = " + ex.toString());
				result = false;
				
				if(m_mongo != null)
				{
					closeConnection();
				}
			}
		}
		
		return result;
	}
	
	
	/**
	 * @method: close the connection to mongo database
	 * @return
	 */
	private boolean closeConnection()
	{
		boolean result = true;
		
		//check if connection existed
		if(m_mongo != null)
		{
			try
			{
				m_mongo.close();
				result = true;
				
			} catch (Exception ex){
				LOGGER.log(Level.SEVERE, "Failed to close connection to mongo, exeption error = " + ex.toString());
				result = false;
			} finally {
				m_mongo = null;
				m_db = null;
				m_collection = null;
			}
		}
		else // connection has already been closed.
		{
			result = true;
		}
		
		return result;
	}
	
	@Override
	public boolean store(GraphObject graph) {		
		boolean result = false;
		
		// open the connection to mongo db
		boolean connected = openConnection();
		if(connected == false)
		{
			LOGGER.log(Level.SEVERE, "Failed to connect to MongoDB for creatring graph: " + graph.getGraphName());
			result = false;
		}
		else
		{
			try{
				
				LOGGER.log(Level.INFO, "Store graph: " + graph.getGraphName() + " to database");
				
				BasicDBObject querryObj = new BasicDBObject();
				querryObj.put("name", graph.getGraphName());
				DBObject object = m_collection.findOne(querryObj);
				
				if(object != null) // graph does exist in database
				{
					LOGGER.log(Level.INFO, "remove: " + graph.getGraphName() + " from database for insert new one");
					m_collection.remove(object);
				}
				
				//create a database object to store it to database.
				BasicDBObject doc = new BasicDBObject();
				doc.put(NAME_FILED, graph.getGraphName());
				doc.put(DATA_FILED, graph.getJsonDescription());
				
				//insert database object to database
				m_collection.insert(doc);
				result = true;
				
			} catch (Exception ex)
			{
				LOGGER.log(Level.SEVERE, "Failed to create graph: " + graph.getGraphName() + "\n Excetion: " + ex.toString());
			} finally{
				closeConnection();
			}			
		}
		
		return result;
	}

	@Override
	public GraphObject retrieve(String graphName) {
		boolean connected = openConnection();
		GraphObject retGraph = null;
		
		if(connected == false) // failed to connect to Mongo DB
		{
			LOGGER.log(Level.SEVERE, "Failed to connect to MongoDB for retrieving graph");
			retGraph =  null;
		}
		else 
		{
			try
			{
				BasicDBObject querryObj = new BasicDBObject();
				querryObj.put("name", graphName);
				DBObject object = m_collection.findOne(querryObj);
				
				if(object != null) // graph does exist in database
				{
					String graphDescription = (String)object.get(DATA_FILED);
					retGraph = new GraphObject(graphName, graphDescription);
				}
				else
				{
					retGraph = null;
				}
				
			} catch (Exception e){
				LOGGER.log(Level.SEVERE, "Failed to retrieve graph.\nException error:" + e.toString());
				retGraph =  null;
			} finally {
				closeConnection();
			}
		}
		
		return retGraph;
	}

	@Override
	public ArrayList<GraphObject> retrieveAll() {
		ArrayList<GraphObject> graphs = new ArrayList<GraphObject>();		
		boolean connected = openConnection();
		
		if(connected == false) // failed to connect to Mongo DB
		{
			LOGGER.log(Level.SEVERE, "Failed to connect to MongoDB for retrieving all graph");
		}
		else 
		{
			try
			{
				//get all entry in database
				DBCursor cursor = m_collection.find();
				String graphDescription;
				String graphName;
				GraphObject graph;
				
				while (cursor.hasNext()) {
					// get a single graph
					DBObject object = cursor.next();
					
					graphName = (String)object.get(NAME_FILED);
					graphDescription = (String)object.get(DATA_FILED);
					graph = new GraphObject(graphName, graphDescription);
					graphs.add(graph);					
				}
				
			} catch (Exception e){
				LOGGER.log(Level.SEVERE, "Failed to retrieve all graphs.\nException error:" + e.toString());
			} finally {
				closeConnection();
			}
		}
		
		return graphs;
	}

	@Override
	public boolean delete(String graphName) {
		boolean result = false;
		boolean connected = openConnection();
		
		if(connected == false) // failed to connect to Mongo DB
		{
			LOGGER.log(Level.SEVERE, "Failed to connect to MongoDB for deleting graph");
			result =  false;
		}
		else 
		{
			try
			{
				BasicDBObject querryObj = new BasicDBObject();
				querryObj.put("name", graphName);
				DBObject object = m_collection.findOne(querryObj);
				
				if(object != null) // graph does exist in database
				{
					m_collection.remove(object);
				}
				
				result = true;
				
			} catch (Exception e){
				LOGGER.log(Level.SEVERE, "Failed to delete graph.\nException error:" + e.toString());
				result =  false;
			} finally {
				closeConnection();
			}
		}
		
		return result;
	}

	@Override
	public boolean deleteAll() {
		boolean result = false;
		
		boolean connected = openConnection();
		if(connected == false) // failed to connect to Mongo DB
		{
			LOGGER.log(Level.SEVERE, "Failed to connect to MongoDB for deleting all graph");
			result = false;
		}
		else 
		{
			try
			{
				DBCursor cursor = m_collection.find();
				
				while (cursor.hasNext()) {
					DBObject object = cursor.next();
					m_collection.remove(object);
				}
				
				result =  true;
			} catch (Exception e){
				LOGGER.log(Level.SEVERE, "Failed to delete all graph.\nException error:" + e.toString());
				result = false;
			} finally {
				closeConnection();
			}
		}
		
		return result;
	}

}
